package com.lanyou.esb.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;

import org.apache.commons.collections4.keyvalue.MultiKey;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import redis.clients.jedis.exceptions.JedisException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.lanyou.cook.common.Constants;
import com.lanyou.cook.datadictionary.UserLogOpt;
import com.lanyou.cook.entity.domain.FieldSystem;
import com.lanyou.cook.entity.domain.userManagement.User;
import com.lanyou.cook.repository.FieldSystemRepository;
import com.lanyou.cook.repository.userManagement.UserRepository;
import com.lanyou.cook.service.SendEmailService;
import com.lanyou.cook.service.UserLogService;
import com.lanyou.esb.common.CommonUtils;
import com.lanyou.esb.datadictionary.EnableType;
import com.lanyou.esb.datadictionary.IfType;
import com.lanyou.esb.datadictionary.TokenStatus;
import com.lanyou.esb.entity.Http;
import com.lanyou.esb.entity.Token;
import com.lanyou.esb.exception.ServiceAccessApplicationException;
import com.lanyou.esb.exception.TokenException;
import com.lanyou.esb.redis.proxy.impl.TokenJedisProxy;
import com.lanyou.esb.redis.vo.IfTokenVo;
import com.lanyou.esb.repository.ServiceChannelManagerRepository;
import com.lanyou.esb.repository.TokenRepository;
import com.lanyou.esb.servicemix.util.FtpRouteHandleUtil;
import com.lanyou.esb.web.param.ServiceAccessApplicationParam;

/**
 * 服务接入
 * 
 * @author Davey.wu
 */
@Service
public class ServiceAccessApplicationService {
	@Autowired
	private FieldSystemRepository fieldSystemRepository;

	@PersistenceContext
	private EntityManager entityManager;

	@Autowired
	private TokenRepository interfaceTokenRepository;

	@Autowired
	private ServiceChannelManagerRepository serviceChannelManagerRepository;

	@Autowired
	private TokenJedisProxy tokenJedisProxy;

	@Autowired
	private UserLogService userLogService;
	
	@Autowired
	private SendEmailService sendEmailService;
	
	@Autowired
	private UserRepository userRepository;

	/**
	 * 获取密钥
	 * 
	 * @param callSystemId
	 * @return
	 */
	public String getApplyKey(String systemId)
			throws ServiceAccessApplicationException {
		FieldSystem system = fieldSystemRepository.findOne(FieldSystem
				.idSpecification(systemId));
		// 未找到相应的领域系统
		if (system == null) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_FIELD_SYSTEM_NOTFOUND,
					CommonUtils.CODE_FIELD_SYSTEM_NOTFOUND);
		}
		return generateUUID();
	}

	/**
	 * 服务接入申请
	 * 
	 * @param param
	 * @param user
	 * @return
	 * @throws ServiceAccessApplicationException
	 */
	@Transactional
	public synchronized void accessApplication(ServiceAccessApplicationParam param, User user)
			throws ServiceAccessApplicationException {

		if (param.getInterfaces() == null || param.getInterfaces().size() == 0) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_INTERFACE_HTTP_EMPTY,
					CommonUtils.CODE_INTERFACE_HTTP_EMPTY);
		}
		// 密钥不能为空
		if (StringUtils.isBlank(param.getApplyKey())) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_INTERFACE_TOKEN_EMPTY,
					CommonUtils.CODE_INTERFACE_TOKEN_EMPTY);
		}
		// 发布方系统
		FieldSystem pubSystem = fieldSystemRepository.findOne(FieldSystem
				.idSpecification(param.getPubSystemId()));
		if (pubSystem == null) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_FIELD_SYSTEM_NOTFOUND + "(发布系统ID："
							+ param.getPubSystemId() + ")",
					CommonUtils.CODE_FIELD_SYSTEM_NOTFOUND);
		}
		// 调用方系统
		FieldSystem callSystem = fieldSystemRepository.findOne(FieldSystem
				.idSpecification(param.getCallSystemId()));
		if (callSystem == null) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_FIELD_SYSTEM_NOTFOUND + "(调用系统ID："
							+ param.getPubSystemId() + ")",
					CommonUtils.CODE_FIELD_SYSTEM_NOTFOUND);
		}
		// 领域系统下是否存在相应的接口
		Map<String, Http> https = getHttp(param);
		if (https == null || https.size() == 0) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_INTERFACE_HTTP_NOTFOUND + "(接口ID:"
							+ param.getInterfaces() + ")",
					CommonUtils.CODE_INTERFACE_HTTP_NOTFOUND);
		}
		StringBuilder builder = new StringBuilder();
		// 判断是否每个接口都存在
		for (String id : param.getInterfaces()) {
			if (StringUtils.isBlank(id)) {
				continue;
			}
			Http http = https.get(id);
			// 接口不存在
			if (http == null) {
				builder.append(id + ",");
			}
		}
		// 有接口不存在，抛出异常
		if (StringUtils.isNotBlank(builder.toString())) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_INTERFACE_HTTP_NOTFOUND + "(接口ID:"
							+ builder.toString() + ")",
					CommonUtils.CODE_INTERFACE_HTTP_NOTFOUND);
		}
		Date now = new Date();
		// 领域系统下接口是否已经申请过
		List<Token> tokenList = getInterfaceToken(param);
		List<Token> oldToken = new ArrayList<Token>(tokenList.size());
		Map<String, Boolean> oldIds = new HashMap<>(tokenList.size());
		builder = new StringBuilder();

		for (Token token : tokenList) {
			// 已申请过密钥，并且已过期或不可用
			if ((token.getKeyInvalidDate() != null && now.after(token
					.getKeyInvalidDate()))
					|| (!EnableType.ENABLE.ValueCode
							.equals(token.getIsEnable()))) {
				oldToken.add(token);
				oldIds.put(token.getHttp().getId(), true);
				continue;
			}
			builder.append(token.getInterfaceName() + ",");
		}
		// 接口中存在已申请的密钥，并且密钥并未过期或可用，不能重复申请
		if (StringUtils.isNotBlank(builder.toString())) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_INTERFACE_HTTP_ACCESS_EXIST + "(接口:"
							+ builder.toString() + ")",
					CommonUtils.CODE_INTERFACE_HTTP_ACCESS_EXIST);
		}
		List<Token> tokenSave = null;
		// 存在密钥并已过期或不可用
		if (oldToken.size() > 0) {
			List<String> ids = new ArrayList<>(oldToken.size());
			for (Token token : oldToken) {
				ids.add(token.getId());
			}
			tokenSave = interfaceTokenRepository.findAll(ids);
		}
		List<Token> tokenAdd = new ArrayList<>();

		Calendar appKeyInvalidTime = Calendar.getInstance();
		appKeyInvalidTime
				.add(Calendar.YEAR, CommonUtils.APPLY_KEY_INVALID_TIME);
		if (tokenSave != null) {
			for (Token token : tokenSave) {
				token.setAppKey(param.getApplyKey());
				token.setKeyInvalidDate(appKeyInvalidTime.getTime());
				token.setIpWhiteList(param.getWhiteList());
				token.setIsEnable(EnableType.ENABLE.ValueCode);
				token.setStatus(TokenStatus.DISABLE.ValueCode);
				token.setCreatedDate(now);
				token.setLastUpdatedDate(now);
				token.setLinkTel(user.getPhoneNumber());
				token.setApplyUserCode(user.getUserCode());
				token.setApplyUserCode(user.getUserCode());
				token.setCreator(user.getId());
				token.setModifier(user.getId());
			}
		}
		for (Http http : https.values()) {
			// 是否已存在
			if (oldIds.get(http.getId()) != null) {
				// 存在则跳过
				continue;
			}
			// 不存在则创建
			Token token = new Token();
			token.setAppKey(param.getApplyKey());
			token.setKeyInvalidDate(appKeyInvalidTime.getTime());
			token.setHttp(http);
			token.setInterfaceCode(http.getCode());
			token.setInterfaceType(http.getType());
			token.setInterfaceVersion(http.getVersion());
			token.setInterfaceName(http.getName());
			token.setInterfacePubSystemId(pubSystem.getId());
			token.setInterfacePubSystemCode(pubSystem.getCode());
			token.setInterfaceCallSystemId(callSystem.getId());
			token.setInterfaceCallSytemCode(callSystem.getCode());
			token.setIpWhiteList(param.getWhiteList());
			token.setIsEnable(EnableType.ENABLE.ValueCode);
			token.setStatus(TokenStatus.DISABLE.ValueCode);
			token.setCreatedDate(now);
			token.setLastUpdatedDate(now);

			token.setLinkTel(user.getPhoneNumber());
			token.setApplyUserCode(user.getUserCode());
			token.setApplyUserCode(user.getUserCode());
			token.setCreator(user.getId());
			token.setModifier(user.getId());
			tokenAdd.add(token);
		}
		// 保存新增的令牌
		interfaceTokenRepository.save(tokenAdd);

		// 更新已存在的令牌
		if (tokenSave != null) {
			interfaceTokenRepository.save(tokenSave);

		}
		if (tokenSave != null) {
			tokenAdd.addAll(tokenSave);
		}
		updateTokenRedisData(tokenAdd);
		
		//将接口根据不同的用户进行分组
		Map<User, String> emailMap = new HashMap<>();
		for (String key : https.keySet()) {
			User userIf = userRepository.findByUserCode(https.get(key).getCreator());
			if(emailMap.containsKey(userIf)){
				//包含这个用户
				String ifName = emailMap.get(userIf);
				emailMap.put(userIf, ifName += (ifName == ""?"":",") + https.get(key).getName());
			}else{
				//不包含
				emailMap.put(userIf, https.get(key).getName());
			}
		}

		// 根据不同的用户发送服务接入申请的邮件
		for (User usr : emailMap.keySet()) {
			Map<String, String> map = new HashMap<String, String>();
			map.put("callPerson", user.getUserCode());
			map.put("callField", callSystem.getFieldName());
			map.put("callSystem", callSystem.getName());
			
			map.put("pubPerson", usr.getUserCode());
			map.put("pubField", pubSystem.getField().getName());
			map.put("pubSystem", pubSystem.getName());
			map.put("ifNames", emailMap.get(usr));
			String requestIpAndPort = FtpRouteHandleUtil.getLocalHost();
			SimpleDateFormat DateTimeFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
			String time = DateTimeFormat.format(new Date()).replace(" ", "#");
			String requestUrl = "http://"+requestIpAndPort+"/pages/UserManagement/UserLogin.html?time=" + time;
			map.put("requestUrl", requestUrl);
			
			//接口发布方邮件地址
			map.put("emailAddress", usr.getEmailAddress());
			try {
				sendEmailService.CookSendEmail(map, Constants.SERVICE_ACESS_APPLICATION_SUBJECT,
						Constants.SERVICE_ACESS_APPLICATION_TEMPLATE);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		

		userLogService.save(UserLogOpt.MaintenanceServiceAccessApplication,
				null, String.format("服务接入申请"));
	}

	/**
	 * 根据服务接入申请参数获取访问令牌信息
	 * 
	 * @param param
	 * @return
	 */
	public List<Token> getInterfaceToken(ServiceAccessApplicationParam param) {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<Tuple> cq = cb.createTupleQuery();

		// 构建FROM和JOIN部分
		Root<Token> rootInterface = cq.from(Token.class);

		// 构建SELECT部分
		cq.multiselect(rootInterface.get("id").alias("id"),
				rootInterface.get("http").get("id").alias("ifId"),
				rootInterface.get("http").get("name").alias("ifName"),
				rootInterface.get("keyInvalidDate").alias("keyInvalidDate"),
				rootInterface.get("isEnable").alias("isEnable"));

		// 构建WHERE部分
		List<Predicate> predicatesList = new ArrayList<>();
		if (StringUtils.isNotBlank(param.getPubSystemId())) {
			predicatesList.add(cb.equal(
					rootInterface.get("interfacePubSystemId"),
					param.getPubSystemId()));
		}
		if (StringUtils.isNotBlank(param.getCallSystemId())) {
			predicatesList.add(cb.equal(
					rootInterface.get("interfaceCallSystemId"),
					param.getCallSystemId()));
		}
		if (param.getInterfaces() != null && param.getInterfaces().size() > 0) {
			predicatesList.add(rootInterface.get("http").get("id")
					.in(param.getInterfaces()));
		}
		if (predicatesList.size() > 0) {
			cq.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
		}
		// 执行查询
		TypedQuery<Tuple> q = entityManager.createQuery(cq);
		List<Tuple> queryResult = q.getResultList();

		if (queryResult == null || queryResult.size() < 1) {
			return new ArrayList<>(0);
		}

		List<Token> list = new ArrayList<>(queryResult.size());
		Token token = new Token();
		Http http;
		for (Tuple tuple : queryResult) {
			token = new Token();
			token.setId((String) tuple.get("id"));
			token.setKeyInvalidDate((Date) tuple.get("keyInvalidDate"));
			token.setInterfaceName((String) tuple.get("ifName"));
			token.setIsEnable((String) tuple.get("isEnable"));
			http = new Http();
			http.setId((String) tuple.get("ifId"));
			http.setName((String) tuple.get("ifName"));
			token.setHttp(http);
			list.add(token);
		}

		return list;
	}

	/**
	 * 成生全球唯一标识
	 * 
	 * @return
	 */
	public String generateUUID() {
		String uuid = UUID.randomUUID().toString().replace("-", "");
		return uuid;
	}

	/**
	 * 根据查询参数获取接口信息，并以接口ID为KEY存入MAP中
	 * 
	 * @param param
	 * @return
	 */
	private Map<String, Http> getHttp(ServiceAccessApplicationParam param) {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<Tuple> cq = cb.createTupleQuery();

		// 构建FROM和JOIN部分
		Root<Http> rootHttp = cq.from(Http.class);

		// 构建SELECT部分
		cq.multiselect(rootHttp.get("id").alias("id"), rootHttp.get("code")
				.alias("code"), rootHttp.get("name").alias("name"), rootHttp
				.get("version").alias("version"),
				rootHttp.get("type").alias("type"), rootHttp.get("ifPublic")
						.alias("ifPublic"), rootHttp.get("creator")
						.alias("creator"));

		// 构建WHERE部分
		List<Predicate> predicatesList = new ArrayList<>();
		if (StringUtils.isNotBlank(param.getPubSystemId())) {
			predicatesList.add(cb.equal(rootHttp.get("fieldSystem").get("id"),
					param.getPubSystemId()));
		}
		if (param.getInterfaces() != null && param.getInterfaces().size() > 0) {
			predicatesList.add(rootHttp.get("id").in(param.getInterfaces()));
		}
		if (predicatesList.size() > 0) {
			cq.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
		}

		// 执行查询
		TypedQuery<Tuple> q = entityManager.createQuery(cq);
		List<Tuple> queryResult = q.getResultList();

		if (queryResult == null || queryResult.size() < 1) {
			return null;
		}
		Map<String, Http> https = new HashMap<String, Http>();

		for (Tuple tuple : queryResult) {
			Http http = new Http();
			http.setId((String) tuple.get("id"));
			http.setCode((String) tuple.get("code"));
			http.setType((String) tuple.get("type"));
			http.setName((String) tuple.get("name"));
			http.setVersion((String) tuple.get("version"));
			http.setIfPublic((String) tuple.get("ifPublic"));
			http.setCreator((String) tuple.get("creator"));
			https.put(http.getId(), http);
		}
		return https;
	}

	/**
	 * 更新访问令牌状态
	 * 
	 * @param id
	 *            :访问令牌标识
	 * @param status
	 *            ：新状态
	 * @return
	 * @throws TokenException
	 * @throws ServiceAccessApplicationException
	 */
	@Transactional
	public void updateStatus(String id, String status) throws TokenException,
			ServiceAccessApplicationException {
		Token token = interfaceTokenRepository.findOne(Token
				.idSpecification(id));
		if (token == null) {
			throw new TokenException("找不到Id为" + id + "的令牌！", "0");
		}

		if (TokenStatus.ENABLE.ValueCode.equals(status)) {
			Date now = new Date();
			Date invalidDate = token.getKeyInvalidDate();
			// 密钥已过期，不能启用
			if (invalidDate != null && invalidDate.before(now)) {
				throw new TokenException("密钥已过期", "0");
			}
		}
		token.setStatus(status);
		interfaceTokenRepository.save(Arrays.asList(token));
		updateTokenRedisData(Arrays.asList(token));

		userLogService.save(UserLogOpt.MaintenanceServiceAccessManagement,
				null, String.format("更新访问令牌状态"));
	}

	/**
	 * 更新密钥失效时间
	 * 
	 * @param id
	 *            ：令牌ID
	 * @param invalidDate
	 *            ：有效期
	 * @return
	 * @throws TokenException
	 * @throws ServiceAccessApplicationException
	 */
	@Transactional
	public void updateKeyInvalidDate(String id, Date invalidDate)
			throws TokenException, ServiceAccessApplicationException {
		Token token = interfaceTokenRepository.findOne(Token
				.idSpecification(id));
		if (token == null) {
			throw new TokenException("找不到Id为" + id + "的令牌！", "0");
		}
		if (token.getCreatedDate() != null) {
			// 失效日期不能在生效期之前
			if (invalidDate.before(token.getCreatedDate())) {
				throw new TokenException("失效日期不能在生效期之前！", "0");
			}
		}
		token.setKeyInvalidDate(invalidDate);
		interfaceTokenRepository.save(Arrays.asList(token));
		updateTokenRedisData(Arrays.asList(token));
		userLogService.save(UserLogOpt.MaintenanceServiceAccessManagement,
				null, String.format("更新密钥失效时间"));
	}

	/**
	 * 分页获取可申请的接口
	 * 
	 * @param userId
	 *            ：用户标识
	 * @param callSystemId
	 *            ：调用方系统标识
	 * @param pubSystemId
	 *            ：发布方系统标识
	 * @param pageNumber
	 *            ：页码
	 * @param pageSize
	 *            ：页大小
	 * @return
	 */
	public List<Http> getAvailableInterface4Page(String userId,
			String callSystemId, String pubSystemId, int pageNumber,
			int pageSize) {
		List<Object[]> list = null;
		// 不分页
		if (pageNumber < 0 || pageSize < 1) {
			list = serviceChannelManagerRepository.getAvailableInterface(
					pubSystemId, callSystemId, userId);
		}
		// 分页
		else {
			list = serviceChannelManagerRepository.getAvailableInterface(
					pubSystemId, callSystemId, userId, pageNumber, pageSize);
		}
		// 无记录，返回空集
		if (list == null) {
			return new ArrayList<>();
		}
		List<Http> result = new ArrayList<Http>(list.size());
		// 转换结果集
		for (Object[] object : list) {
			Http http = new Http();
			http.setId((String) object[0]);
			http.setName((String) object[1]);
			http.setCode((String) object[2]);
			result.add(http);
		}
		return result;
	}

	/**
	 * 新增或修改Redis中对应数据类
	 * 
	 * @param tokens
	 * @return
	 * @throws ServiceAccessApplicationException
	 */
	private boolean updateTokenRedisData(List<Token> tokens)
			throws ServiceAccessApplicationException {
		try {
			for (Token token : tokens) {
				IfTokenVo target = IfTokenVo.getInstance(token);
				tokenJedisProxy.hset(
						tokenJedisProxy.generateKeyFromObject(target), target);
			}
		} catch (JsonProcessingException e) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_JSON_PARSING_ERROR,
					CommonUtils.CODE_JSON_PARSING_ERROR);
		} catch (JedisException e) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_CONNECT_REDIS_FAIED,
					CommonUtils.CODE_CONNECT_REDIS_FAIED);
		} catch (Exception e) {
			throw new ServiceAccessApplicationException(
					CommonUtils.MSG_UPDATE_REDIS_DATA_FAIED,
					CommonUtils.CODE_UPDATE_REDIS_DATA_FAIED);
		}
		return true;
	}
}
